"""
Tests the InvokeContext class
"""

import errno
import os

from parameterized import parameterized

from samcli.lib.utils.packagetype import ZIP
from samcli.commands._utils.template import TemplateFailedParsingException
from samcli.commands.local.cli_common.invoke_context import (
    InvokeContext,
    ContainersInitializationMode,
    ContainersMode,
    DebugContextException,
    NoFunctionIdentifierProvidedException,
    InvalidEnvironmentVariablesFileException,
)
from samcli.local.lambdafn.exceptions import FunctionNotFound
from samcli.local.docker.exceptions import ContainerNotReachableException

from unittest import TestCase
from unittest.mock import Mock, patch, ANY, mock_open, call

from samcli.lib.providers.provider import Stack
import pytest

# Mock container runtime availability for consistent behavior across environments
# These can be overridden in individual tests that need to test failure scenarios
_admin_preference_patcher = patch(
    "samcli.local.docker.container_client_factory.ContainerClientFactory.get_admin_container_preference",
    return_value=None,
)


# use fixture to avoid noisy neighbor problem
@pytest.fixture(scope="module", autouse=True)
def setup_module_patches():
    """Setup and teardown module-level patches"""
    _admin_preference_patcher.start()

    yield

    _admin_preference_patcher.stop()


class TestInvokeContext__enter__(TestCase):
    @patch(
        "samcli.local.docker.container_client_factory.ContainerClientFactory.get_admin_container_preference",
        return_value=None,
    )
    @patch("samcli.local.docker.container_client_factory.ContainerClientFactory.create_client")
    @patch("samcli.commands.local.cli_common.invoke_context.ContainerManager")
    @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider")
    @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext._add_account_id_to_global")
    def test_must_read_from_necessary_files(
        self,
        _add_account_id_to_global_mock,
        SamFunctionProviderMock,
        ContainerManagerMock,
        mock_create_client,
        mock_enterprise,
    ):
        function_provider = Mock()
        function_provider.get_all.return_value = [
            Mock(
                functionname="name",
                function_id="id",
                handler="app.handler",
                runtime="test",
                packagetype=ZIP,
                inlinecode="| \
                exports.handler = async () => 'Hello World!'",
            )
        ]

        SamFunctionProviderMock.return_value = function_provider

        template_file = "template_file"
        env_vars_file = "env_vars_file"
        log_file = "log_file"

        invoke_context = InvokeContext(
            template_file=template_file,
            function_identifier="id",
            env_vars_file=env_vars_file,
            docker_volume_basedir="volumedir",
            docker_network="network",
            log_file=log_file,
            skip_pull_image=True,
            debug_ports=[1111],
            debugger_path="path-to-debugger",
            debug_args="args",
            parameter_overrides={},
            aws_region="region",
            aws_profile="profile",
            shutdown=False,
            invoke_images={None: "image"},
        )

        template_dict = "template_dict"
        stacks = [Stack("", "", template_file, Mock(), template_dict)]
        invoke_context._get_stacks = Mock()
        invoke_context._get_stacks.return_value = stacks

        env_vars_value = "env_vars_value"
        invoke_context._get_env_vars_value = Mock()
        invoke_context._get_env_vars_value.return_value = env_vars_value

        log_file_handle = "handle"
        invoke_context._setup_log_file = Mock()
        invoke_context._setup_log_file.return_value = log_file_handle

        debug_context_mock = Mock()
        invoke_context._get_debug_context = Mock()
        invoke_context._get_debug_context.return_value = debug_context_mock

        container_manager_mock = Mock()

        ContainerManagerMock.return_value = container_manager_mock

        # Call Enter method manually for testing purposes
        result = invoke_context.__enter__()
        self.assertTrue(result is invoke_context, "__enter__() must return self")

        function_provider.get_all.assert_called_once()
        self.assertEqual(invoke_context._function_provider, function_provider)
        self.assertEqual(invoke_context._env_vars_value, env_vars_value)
        self.assertEqual(invoke_context._log_file_handle, log_file_handle)
        self.assertEqual(invoke_context._debug_context, debug_context_mock)
        self.assertEqual(invoke_context._container_manager, container_manager_mock)
        self.assertEqual(invoke_context._containers_mode, ContainersMode.COLD)
        self.assertEqual(invoke_context._containers_initializing_mode, ContainersInitializationMode.LAZY)
        self.assertEqual(invoke_context._invoke_images, {None: "image"})

        invoke_context._get_stacks.assert_called_once()
        SamFunctionProviderMock.assert_called_with(stacks, True)
        self.assertEqual(invoke_context._global_parameter_overrides, {"AWS::Region": "region"})
        self.assertEqual(invoke_context._get_env_vars_value.call_count, 2)
        self.assertEqual(invoke_context._get_env_vars_value.call_args_list, [call(env_vars_file), call(None)])
        invoke_context._setup_log_file.assert_called_with(log_file)
        invoke_context._get_debug_context.assert_called_once_with(
            [1111], "args", "path-to-debugger", "env_vars_value", None
        )
        ContainerManagerMock.assert_called_once_with(
            docker_network_id="network", skip_pull_image=True, do_shutdown_event=False
        )

    @patch("samcli.commands.local.cli_common.invoke_context.ContainerManager")
    @patch("samcli.commands.local.cli_common.invoke_context.RefreshableSamFunctionProvider")
    @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext._add_account_id_to_global")
    def test_must_initialize_all_containers_if_warm_containers_is_enabled(
        self, _add_account_id_to_global_mock, RefreshableSamFunctionProviderMock, ContainerManagerMock
    ):
        function_provider = Mock()
        function = Mock()
        function_provider.get_all.return_value = [function]
        function_provider.functions = {}
        RefreshableSamFunctionProviderMock.return_value = function_provider

        template_file = "template_file"
        env_vars_file = "env_vars_file"
        log_file = "log_file"
        parameter_overrides = {}
        global_parameter_overrides = {"AWS::Region": "region"}

        invoke_context = InvokeContext(
            template_file=template_file,
            function_identifier="id",
            env_vars_file=env_vars_file,
            docker_volume_basedir="volumedir",
            docker_network="network",
            log_file=log_file,
            skip_pull_image=True,
            debug_ports=[1111],
            debugger_path="path-to-debugger",
            debug_args="args",
            parameter_overrides=parameter_overrides,
            aws_region="region",
            aws_profile="profile",
            warm_container_initialization_mode=ContainersInitializationMode.EAGER.value,
            shutdown=True,
            invoke_images={None: "image"},
        )

        _initialize_all_functions_containers_mock = Mock()
        invoke_context._initialize_all_functions_containers = _initialize_all_functions_containers_mock

        template_dict = "template_dict"
        stacks = [Stack("", "", template_file, Mock(), template_dict)]
        invoke_context._get_stacks = Mock()
        invoke_context._get_stacks.return_value = stacks

        env_vars_value = "env_vars_value"
        invoke_context._get_env_vars_value = Mock()
        invoke_context._get_env_vars_value.return_value = env_vars_value

        log_file_handle = "handle"
        invoke_context._setup_log_file = Mock()
        invoke_context._setup_log_file.return_value = log_file_handle

        debug_context_mock = Mock()
        invoke_context._get_debug_context = Mock()
        invoke_context._get_debug_context.return_value = debug_context_mock

        container_manager_mock = Mock()

        ContainerManagerMock.return_value = container_manager_mock

        # Call Enter method manually for testing purposes
        result = invoke_context.__enter__()
        self.assertTrue(result is invoke_context, "__enter__() must return self")

        self.assertEqual(invoke_context._function_provider, function_provider)
        self.assertEqual(invoke_context._env_vars_value, env_vars_value)
        self.assertEqual(invoke_context._log_file_handle, log_file_handle)
        self.assertEqual(invoke_context._debug_context, debug_context_mock)
        self.assertEqual(invoke_context._container_manager, container_manager_mock)
        self.assertEqual(invoke_context._containers_mode, ContainersMode.WARM)
        self.assertEqual(invoke_context._containers_initializing_mode, ContainersInitializationMode.EAGER)
        self.assertEqual(invoke_context._invoke_images, {None: "image"})

        invoke_context._get_stacks.assert_called_once()
        RefreshableSamFunctionProviderMock.assert_called_with(
            stacks, parameter_overrides, global_parameter_overrides, True
        )
        self.assertEqual(invoke_context._global_parameter_overrides, global_parameter_overrides)
        self.assertEqual(invoke_context._get_env_vars_value.call_count, 2)
        self.assertEqual(invoke_context._get_env_vars_value.call_args_list, [call(env_vars_file), call(None)])
        invoke_context._setup_log_file.assert_called_with(log_file)
        invoke_context._get_debug_context.assert_called_once_with(
            None, "args", "path-to-debugger", "env_vars_value", None
        )
        ContainerManagerMock.assert_called_once_with(
            docker_network_id="network", skip_pull_image=True, do_shutdown_event=True
        )
        _initialize_all_functions_containers_mock.assert_called_once_with()

    @patch("samcli.commands.local.cli_common.invoke_context.ContainerManager")
    @patch("samcli.commands.local.cli_common.invoke_context.RefreshableSamFunctionProvider")
    @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext._add_account_id_to_global")
    def test_must_set_debug_function_if_warm_containers_enabled_no_debug_function_provided_and_template_contains_one_function(
        self, _add_account_id_to_global_mock, RefreshableSamFunctionProviderMock, ContainerManagerMock
    ):
        function_provider = Mock()
        function = Mock(
            functionname="function_name", handler="app.handler", runtime="test", packagetype=ZIP, inlinecode=None
        )
        function_provider.functions = {"function_name": function}
        function_provider.get_all.return_value = [function]
        RefreshableSamFunctionProviderMock.return_value = function_provider

        template_file = "template_file"
        env_vars_file = "env_vars_file"
        container_env_vars_file = "container_env_vars_file"
        log_file = "log_file"
        parameter_overrides = {}
        global_parameter_overrides = {"AWS::Region": "region"}

        invoke_context = InvokeContext(
            template_file=template_file,
            function_identifier="id",
            env_vars_file=env_vars_file,
            docker_volume_basedir="volumedir",
            docker_network="network",
            log_file=log_file,
            skip_pull_image=True,
            debug_ports=[1111],
            debugger_path="path-to-debugger",
            container_env_vars_file=container_env_vars_file,
            debug_args="args",
            parameter_overrides=parameter_overrides,
            aws_region="region",
            aws_profile="profile",
            warm_container_initialization_mode=ContainersInitializationMode.EAGER.value,
            debug_function="",
            shutdown=True,
            invoke_images={None: "image"},
        )

        _initialize_all_functions_containers_mock = Mock()
        invoke_context._initialize_all_functions_containers = _initialize_all_functions_containers_mock

        template_dict = "template_dict"
        stacks = [Stack("", "", template_file, Mock(), template_dict)]
        invoke_context._get_stacks = Mock()
        invoke_context._get_stacks.return_value = stacks

        invoke_context._get_env_vars_value = Mock(side_effect=["Env var value", "Debug env var value"])

        log_file_handle = "handle"
        invoke_context._setup_log_file = Mock()
        invoke_context._setup_log_file.return_value = log_file_handle

        debug_context_mock = Mock()
        invoke_context._get_debug_context = Mock()
        invoke_context._get_debug_context.return_value = debug_context_mock

        container_manager_mock = Mock()

        ContainerManagerMock.return_value = container_manager_mock

        # Call Enter method manually for testing purposes
        result = invoke_context.__enter__()
        self.assertTrue(result is invoke_context, "__enter__() must return self")

        self.assertEqual(invoke_context._function_provider, function_provider)
        self.assertEqual(invoke_context._env_vars_value, "Env var value")
        self.assertEqual(invoke_context._container_env_vars_value, "Debug env var value")
        self.assertEqual(invoke_context._log_file_handle, log_file_handle)
        self.assertEqual(invoke_context._debug_context, debug_context_mock)
        self.assertEqual(invoke_context._container_manager, container_manager_mock)
        self.assertEqual(invoke_context._containers_mode, ContainersMode.WARM)
        self.assertEqual(invoke_context._containers_initializing_mode, ContainersInitializationMode.EAGER)
        self.assertEqual(invoke_context._invoke_images, {None: "image"})

        invoke_context._get_stacks.assert_called_once()
        RefreshableSamFunctionProviderMock.assert_called_with(
            stacks, parameter_overrides, global_parameter_overrides, True
        )
        self.assertEqual(invoke_context._global_parameter_overrides, global_parameter_overrides)
        self.assertEqual(invoke_context._get_env_vars_value.call_count, 2)
        self.assertEqual(
            invoke_context._get_env_vars_value.call_args_list, [call("env_vars_file"), call("container_env_vars_file")]
        )
        invoke_context._setup_log_file.assert_called_with(log_file)
        invoke_context._get_debug_context.assert_called_once_with(
            [1111], "args", "path-to-debugger", "Debug env var value", "function_name"
        )
        ContainerManagerMock.assert_called_once_with(
            docker_network_id="network", skip_pull_image=True, do_shutdown_event=True
        )
        _initialize_all_functions_containers_mock.assert_called_once_with()

    @patch("samcli.commands.local.cli_common.invoke_context.ContainerManager")
    @patch("samcli.commands.local.cli_common.invoke_context.RefreshableSamFunctionProvider")
    @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext._add_account_id_to_global")
    def test_no_container_will_be_initialized_if_lazy_containers_is_enabled(
        self, _add_account_id_to_global_mock, RefreshableSamFunctionProviderMock, ContainerManagerMock
    ):
        function_provider = Mock()
        function_provider.get_all.return_value = [
            Mock(functionname="function_name", handler="app.handler", runtime="test", packagetype=ZIP, inlinecode=None)
        ]

        RefreshableSamFunctionProviderMock.return_value = function_provider

        template_file = "template_file"
        env_vars_file = "env_vars_file"
        log_file = "log_file"
        parameter_overrides = {}
        global_parameter_overrides = {"AWS::Region": "region"}

        invoke_context = InvokeContext(
            template_file=template_file,
            function_identifier="id",
            env_vars_file=env_vars_file,
            docker_volume_basedir="volumedir",
            docker_network="network",
            log_file=log_file,
            skip_pull_image=True,
            debug_ports=[1111],
            debugger_path="path-to-debugger",
            debug_args="args",
            parameter_overrides=parameter_overrides,
            aws_region="region",
            aws_profile="profile",
            warm_container_initialization_mode=ContainersInitializationMode.LAZY.value,
            debug_function="debug_function",
            shutdown=True,
            invoke_images={None: "image"},
        )

        template_dict = "template_dict"
        stacks = [Stack("", "", template_file, Mock(), template_dict)]
        invoke_context._get_stacks = Mock()
        invoke_context._get_stacks.return_value = stacks

        env_vars_value = "env_vars_value"
        invoke_context._get_env_vars_value = Mock()
        invoke_context._get_env_vars_value.return_value = env_vars_value

        log_file_handle = "handle"
        invoke_context._setup_log_file = Mock()
        invoke_context._setup_log_file.return_value = log_file_handle

        debug_context_mock = Mock()
        invoke_context._get_debug_context = Mock()
        invoke_context._get_debug_context.return_value = debug_context_mock

        container_manager_mock = Mock()

        ContainerManagerMock.return_value = container_manager_mock

        # Call Enter method manually for testing purposes
        result = invoke_context.__enter__()
        self.assertTrue(result is invoke_context, "__enter__() must return self")

        self.assertEqual(invoke_context._function_provider, function_provider)
        self.assertEqual(invoke_context._env_vars_value, env_vars_value)
        self.assertEqual(invoke_context._log_file_handle, log_file_handle)
        self.assertEqual(invoke_context._debug_context, debug_context_mock)
        self.assertEqual(invoke_context._container_manager, container_manager_mock)
        self.assertEqual(invoke_context._containers_mode, ContainersMode.WARM)
        self.assertEqual(invoke_context._containers_initializing_mode, ContainersInitializationMode.LAZY)
        self.assertEqual(invoke_context._invoke_images, {None: "image"})

        invoke_context._get_stacks.assert_called_once()
        RefreshableSamFunctionProviderMock.assert_called_with(
            stacks, parameter_overrides, global_parameter_overrides, True
        )
        self.assertEqual(invoke_context._global_parameter_overrides, global_parameter_overrides)
        self.assertEqual(invoke_context._get_env_vars_value.call_count, 2)
        self.assertEqual(invoke_context._get_env_vars_value.call_args_list, [call(env_vars_file), call(None)])
        invoke_context._setup_log_file.assert_called_with(log_file)
        invoke_context._get_debug_context.assert_called_once_with(
            [1111], "args", "path-to-debugger", "env_vars_value", "debug_function"
        )
        ContainerManagerMock.assert_called_once_with(
            docker_network_id="network", skip_pull_image=True, do_shutdown_event=True
        )

    @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider")
    def test_must_use_container_manager_to_check_docker_connectivity(self, SamFunctionProviderMock):
        invoke_context = InvokeContext("template-file")

        invoke_context._get_stacks = Mock()
        invoke_context._get_stacks.return_value = [Mock()]
        invoke_context._get_env_vars_value = Mock()
        invoke_context._setup_log_file = Mock()
        invoke_context._get_debug_context = Mock()

        container_manager_mock = Mock()
        invoke_context._get_container_manager = Mock()
        invoke_context._get_container_manager.return_value = container_manager_mock

        # This should succeed with our mocked container runtime availability
        invoke_context.__enter__()

    @parameterized.expand(
        [
            (
                "Windows",
                "Running AWS SAM projects locally requires a container runtime. Do you have Docker installed and running?",
            ),
            (
                "Darwin",
                "Running AWS SAM projects locally requires a container runtime. Do you have Docker or Finch installed and running?",
            ),
            (
                "Linux",
                "Running AWS SAM projects locally requires a container runtime. Do you have Docker or Finch installed and running?",
            ),
        ]
    )
    @patch("samcli.local.docker.utils.get_validated_container_client")
    @patch(
        "samcli.local.docker.container_client_factory.ContainerClientFactory.get_admin_container_preference",
        return_value=None,
    )
    @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider")
    def test_must_raise_if_docker_is_not_reachable(
        self,
        platform_name,
        expected_message,
        SamFunctionProviderMock,
        mock_platform,
        mock_get_validated_client,
    ):
        mock_platform.return_value = platform_name
        # Mock the get_validated_container_client to raise ContainerNotReachableException
        mock_get_validated_client.side_effect = ContainerNotReachableException(expected_message)

        invoke_context = InvokeContext("template-file")

        invoke_context._get_stacks = Mock()
        invoke_context._get_stacks.return_value = [Mock()]
        invoke_context._get_env_vars_value = Mock()
        invoke_context._setup_log_file = Mock()
        invoke_context._get_debug_context = Mock()

        with self.assertRaises(ContainerNotReachableException) as ex_ctx:
            invoke_context.__enter__()

        self.assertEqual(expected_message, str(ex_ctx.exception))

    @patch("samcli.commands.local.cli_common.invoke_context.SamLocalStackProvider.get_stacks")
    def test_must_raise_if_template_cannot_be_parsed(self, get_buildable_stacks_mock):
        invoke_context = InvokeContext("template-file")

        get_buildable_stacks_mock.side_effect = TemplateFailedParsingException("")
        with self.assertRaises(TemplateFailedParsingException) as ex_ctx:
            invoke_context.__enter__()

    @parameterized.expand(
        [
            (None, "/my/cool/path", True),
            ("LAZY", "/my/cool/path", True),
            (None, None, False),
        ]
    )
    @patch("samcli.lib.providers.sam_function_provider.SamFunctionProvider._extract_functions")
    @patch("samcli.lib.utils.file_observer.SingletonFileObserver.start")
    def test_docker_volume_basedir_set_use_raw_codeuri(
        self, container_mode, docker_volume_basedir, expected, observer_mock, extract_func_mock
    ):
        invoke_context = InvokeContext(
            "template",
            warm_container_initialization_mode=container_mode,
            docker_volume_basedir=docker_volume_basedir,
            shutdown=True,
        )

        invoke_context._initialize_all_functions_containers = Mock()
        invoke_context._get_container_manager = Mock(return_value=Mock())
        invoke_context._get_debug_context = Mock(return_value=Mock())
        invoke_context._get_stacks = Mock(return_value=[])

        invoke_context.__enter__()

        extract_func_mock.assert_called_with([], expected, False, False, None)


class TestInvokeContext__exit__(TestCase):
    def test_must_close_opened_logfile(self):
        context = InvokeContext(template_file="template")
        handle_mock = Mock()
        context._log_file_handle = handle_mock

        # Mock lambda runtime for durable cleanup
        runtime_mock = Mock()
        context._lambda_runtimes = {context._containers_mode: runtime_mock}

        context.__exit__()

        handle_mock.close.assert_called_with()
        self.assertIsNone(context._log_file_handle)
        runtime_mock.clean_runtime_containers.assert_called_once()

    def test_must_ignore_if_handle_is_absent(self):
        context = InvokeContext(template_file="template")
        context._log_file_handle = None

        # Mock lambda runtime for durable cleanup
        runtime_mock = Mock()
        context._lambda_runtimes = {context._containers_mode: runtime_mock}

        context.__exit__()

        self.assertIsNone(context._log_file_handle)
        runtime_mock.clean_runtime_containers.assert_called_once()

    def test_must_cleanup_durable_containers(self):
        context = InvokeContext(template_file="template")

        # Mock lambda runtime
        runtime_mock = Mock()
        context._lambda_runtimes = {context._containers_mode: runtime_mock}

        context.__exit__()

        # Verify runtime containers cleanup method was called
        runtime_mock.clean_runtime_containers.assert_called_once()


class TestInvokeContextAsContextManager(TestCase):
    """
    Must be able to use the class as a context manager
    """

    @patch.object(InvokeContext, "__enter__")
    @patch.object(InvokeContext, "__exit__")
    @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext._add_account_id_to_global")
    def test_must_work_in_with_statement(self, _add_account_id_to_global_mock, ExitMock, EnterMock):
        context_obj = Mock()
        EnterMock.return_value = context_obj

        with InvokeContext(
            template_file="template_file",
            function_identifier="id",
            env_vars_file="env_vars_file",
            docker_volume_basedir="volumedir",
            docker_network="network",
            log_file="log_file",
            skip_pull_image=True,
            debug_ports=[1111],
            debugger_path="path-to-debugger",
            debug_args="args",
            aws_profile="profile",
            invoke_images={None: "image"},
        ) as context:
            self.assertEqual(context_obj, context)

        EnterMock.assert_called_with()
        self.assertEqual(1, ExitMock.call_count)


class TestInvokeContext_function_name_property(TestCase):
    def test_must_return_function_name_if_present(self):
        id = "id"
        context = InvokeContext(template_file="template_file", function_identifier=id)

        self.assertEqual(id, context.function_identifier)

    def test_must_return_one_function_from_template(self):
        context = InvokeContext(template_file="template_file")

        function = Mock()
        function.name = "myname"
        context._function_provider = Mock()
        context._function_provider.get_all.return_value = [function]  # Provider returns only one function

        self.assertEqual("myname", context.function_identifier)

    def test_must_raise_if_more_than_one_function(self):
        context = InvokeContext(template_file="template_file")

        context._function_provider = Mock()
        context._function_provider.get_all.return_value = [Mock(), Mock(), Mock()]  # Provider returns three functions

        with self.assertRaises(NoFunctionIdentifierProvidedException):
            context.function_identifier


class TestInvokeContext_local_lambda_runner(TestCase):
    @patch("samcli.local.lambdafn.runtime.LambdaFunctionObserver")
    @patch("samcli.commands.local.cli_common.invoke_context.LambdaImage")
    @patch("samcli.commands.local.cli_common.invoke_context.LayerDownloader")
    @patch("samcli.commands.local.cli_common.invoke_context.LambdaRuntime")
    @patch("samcli.commands.local.cli_common.invoke_context.LocalLambdaRunner")
    @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider")
    @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext._add_account_id_to_global")
    def test_must_create_runner(
        self,
        _add_account_id_to_global_mock,
        SamFunctionProviderMock,
        LocalLambdaMock,
        LambdaRuntimeMock,
        download_layers_mock,
        lambda_image_patch,
        LambdaFunctionObserver_patch,
    ):
        runtime_mock = Mock()
        LambdaRuntimeMock.return_value = runtime_mock

        runner_mock = Mock()
        LocalLambdaMock.return_value = runner_mock

        download_mock = Mock()
        download_layers_mock.return_value = download_mock

        image_mock = Mock()
        lambda_image_patch.return_value = image_mock

        LambdaFunctionObserver_mock = Mock()
        LambdaFunctionObserver_patch.return_value = LambdaFunctionObserver_mock

        cwd = "cwd"
        self.context = InvokeContext(
            template_file="template_file",
            function_identifier="id",
            env_vars_file="env_vars_file",
            docker_volume_basedir="volumedir",
            docker_network="network",
            log_file="log_file",
            skip_pull_image=True,
            force_image_build=True,
            debug_ports=[1111],
            debugger_path="path-to-debugger",
            debug_args="args",
            aws_profile="profile",
            aws_region="region",
            mount_symlinks=True,
            no_mem_limit=True,
        )
        self.context.get_cwd = Mock()
        self.context.get_cwd.return_value = cwd

        self.context._get_stacks = Mock()
        self.context._get_stacks.return_value = [Mock()]
        self.context._get_env_vars_value = Mock()
        self.context._setup_log_file = Mock()
        self.context._get_debug_context = Mock(return_value=None)

        container_manager_mock = Mock()

        self.context._get_container_manager = Mock(return_value=container_manager_mock)

        with self.context:
            result = self.context.local_lambda_runner
            self.assertEqual(result, runner_mock)

            LambdaRuntimeMock.assert_called_with(
                container_manager_mock, image_mock, mount_symlinks=True, no_mem_limit=True
            )
            lambda_image_patch.assert_called_once_with(download_mock, True, True, invoke_images=None)
            LocalLambdaMock.assert_called_with(
                local_runtime=runtime_mock,
                function_provider=ANY,
                cwd=cwd,
                real_path=ANY,
                debug_context=None,
                env_vars_values=ANY,
                aws_profile="profile",
                aws_region="region",
                container_host=None,
                container_host_interface=None,
                extra_hosts=None,
            )

            result = self.context.local_lambda_runner
            self.assertEqual(result, runner_mock)
            # assert that lambda runner is created only one time, and the cached version used in the second call
            self.assertEqual(LocalLambdaMock.call_count, 1)

    @patch("samcli.commands.local.cli_common.invoke_context.LambdaImage")
    @patch("samcli.commands.local.cli_common.invoke_context.LayerDownloader")
    @patch("samcli.commands.local.cli_common.invoke_context.WarmLambdaRuntime")
    @patch("samcli.commands.local.cli_common.invoke_context.LocalLambdaRunner")
    @patch("samcli.commands.local.cli_common.invoke_context.RefreshableSamFunctionProvider")
    @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext._add_account_id_to_global")
    def test_must_create_runner_using_warm_containers(
        self,
        _add_account_id_to_global_mock,
        RefreshableSamFunctionProviderMock,
        LocalLambdaMock,
        WarmLambdaRuntimeMock,
        download_layers_mock,
        lambda_image_patch,
    ):
        runtime_mock = Mock()
        WarmLambdaRuntimeMock.return_value = runtime_mock

        runner_mock = Mock()
        LocalLambdaMock.return_value = runner_mock

        download_mock = Mock()
        download_layers_mock.return_value = download_mock

        image_mock = Mock()
        lambda_image_patch.return_value = image_mock

        cwd = "cwd"
        self.context = InvokeContext(
            template_file="template_file",
            function_identifier="id",
            env_vars_file="env_vars_file",
            docker_volume_basedir="volumedir",
            docker_network="network",
            log_file="log_file",
            skip_pull_image=True,
            force_image_build=True,
            debug_ports=[1111],
            debugger_path="path-to-debugger",
            debug_args="args",
            aws_profile="profile",
            aws_region="region",
            warm_container_initialization_mode=ContainersInitializationMode.EAGER,
            mount_symlinks=False,
            no_mem_limit=False,
        )
        self.context.get_cwd = Mock()
        self.context.get_cwd.return_value = cwd

        self.context._get_stacks = Mock()
        self.context._get_stacks.return_value = [Mock()]
        self.context._get_env_vars_value = Mock()
        self.context._setup_log_file = Mock()
        self.context._get_debug_context = Mock(return_value=None)

        container_manager_mock = Mock()

        self.context._get_container_manager = Mock(return_value=container_manager_mock)

        with self.context:
            result = self.context.local_lambda_runner
            self.assertEqual(result, runner_mock)

            WarmLambdaRuntimeMock.assert_called_with(
                container_manager_mock, image_mock, mount_symlinks=False, no_mem_limit=False
            )
            lambda_image_patch.assert_called_once_with(download_mock, True, True, invoke_images=None)
            LocalLambdaMock.assert_called_with(
                local_runtime=runtime_mock,
                function_provider=ANY,
                cwd=cwd,
                real_path=ANY,
                debug_context=None,
                env_vars_values=ANY,
                aws_profile="profile",
                aws_region="region",
                container_host=None,
                container_host_interface=None,
                extra_hosts=None,
            )

            result = self.context.local_lambda_runner
            self.assertEqual(result, runner_mock)
            # assert that lambda runner is created only one time, and the cached version used in the second call
            self.assertEqual(LocalLambdaMock.call_count, 1)

    @patch("samcli.local.lambdafn.runtime.LambdaFunctionObserver")
    @patch("samcli.commands.local.cli_common.invoke_context.LambdaImage")
    @patch("samcli.commands.local.cli_common.invoke_context.LayerDownloader")
    @patch("samcli.commands.local.cli_common.invoke_context.LambdaRuntime")
    @patch("samcli.commands.local.cli_common.invoke_context.LocalLambdaRunner")
    @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider")
    @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext._add_account_id_to_global")
    def test_must_create_runner_with_container_host_option(
        self,
        _add_account_id_to_global_mock,
        SamFunctionProviderMock,
        LocalLambdaMock,
        LambdaRuntimeMock,
        download_layers_mock,
        lambda_image_patch,
        LambdaFunctionObserver_patch,
    ):
        runtime_mock = Mock()
        LambdaRuntimeMock.return_value = runtime_mock

        runner_mock = Mock()
        LocalLambdaMock.return_value = runner_mock

        download_mock = Mock()
        download_layers_mock.return_value = download_mock

        image_mock = Mock()
        lambda_image_patch.return_value = image_mock

        LambdaFunctionObserver_mock = Mock()
        LambdaFunctionObserver_patch.return_value = LambdaFunctionObserver_mock

        cwd = "cwd"
        self.context = InvokeContext(
            template_file="template_file",
            function_identifier="id",
            env_vars_file="env_vars_file",
            docker_volume_basedir="volumedir",
            docker_network="network",
            log_file="log_file",
            skip_pull_image=True,
            force_image_build=True,
            debug_ports=[1111],
            debugger_path="path-to-debugger",
            debug_args="args",
            aws_profile="profile",
            aws_region="region",
            container_host="abcdef",
            container_host_interface="192.168.100.101",
            mount_symlinks=False,
            no_mem_limit=False,
        )
        self.context.get_cwd = Mock()
        self.context.get_cwd.return_value = cwd

        self.context._get_stacks = Mock()
        self.context._get_stacks.return_value = [Mock()]
        self.context._get_env_vars_value = Mock()
        self.context._setup_log_file = Mock()
        self.context._get_debug_context = Mock(return_value=None)

        container_manager_mock = Mock()

        self.context._get_container_manager = Mock(return_value=container_manager_mock)

        with self.context:
            result = self.context.local_lambda_runner
            self.assertEqual(result, runner_mock)

            LambdaRuntimeMock.assert_called_with(
                container_manager_mock, image_mock, mount_symlinks=False, no_mem_limit=False
            )
            lambda_image_patch.assert_called_once_with(download_mock, True, True, invoke_images=None)
            LocalLambdaMock.assert_called_with(
                local_runtime=runtime_mock,
                function_provider=ANY,
                cwd=cwd,
                real_path=ANY,
                debug_context=None,
                env_vars_values=ANY,
                aws_profile="profile",
                aws_region="region",
                container_host="abcdef",
                container_host_interface="192.168.100.101",
                extra_hosts=None,
            )

            result = self.context.local_lambda_runner
            self.assertEqual(result, runner_mock)
            # assert that lambda runner is created only one time, and the cached version used in the second call
            self.assertEqual(LocalLambdaMock.call_count, 1)

    @patch("samcli.local.lambdafn.runtime.LambdaFunctionObserver")
    @patch("samcli.commands.local.cli_common.invoke_context.LambdaImage")
    @patch("samcli.commands.local.cli_common.invoke_context.LayerDownloader")
    @patch("samcli.commands.local.cli_common.invoke_context.LambdaRuntime")
    @patch("samcli.commands.local.cli_common.invoke_context.LocalLambdaRunner")
    @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider")
    @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext._add_account_id_to_global")
    def test_must_create_runner_with_extra_hosts_option(
        self,
        _add_account_id_to_global_mock,
        SamFunctionProviderMock,
        LocalLambdaMock,
        LambdaRuntimeMock,
        download_layers_mock,
        lambda_image_patch,
        LambdaFunctionObserver_patch,
    ):
        runtime_mock = Mock()
        LambdaRuntimeMock.return_value = runtime_mock

        runner_mock = Mock()
        LocalLambdaMock.return_value = runner_mock

        download_mock = Mock()
        download_layers_mock.return_value = download_mock

        image_mock = Mock()
        lambda_image_patch.return_value = image_mock

        LambdaFunctionObserver_mock = Mock()
        LambdaFunctionObserver_patch.return_value = LambdaFunctionObserver_mock

        cwd = "cwd"
        self.context = InvokeContext(
            template_file="template_file",
            function_identifier="id",
            env_vars_file="env_vars_file",
            docker_volume_basedir="volumedir",
            docker_network="network",
            log_file="log_file",
            skip_pull_image=True,
            force_image_build=True,
            debug_ports=[1111],
            debugger_path="path-to-debugger",
            debug_args="args",
            aws_profile="profile",
            aws_region="region",
            container_host="abcdef",
            add_host={"prod-na.host": "10.11.12.13", "gamma-na.host": "10.22.23.24"},
            mount_symlinks=False,
            no_mem_limit=False,
        )
        self.context.get_cwd = Mock()
        self.context.get_cwd.return_value = cwd

        self.context._get_stacks = Mock()
        self.context._get_stacks.return_value = [Mock()]
        self.context._get_env_vars_value = Mock()
        self.context._setup_log_file = Mock()
        self.context._get_debug_context = Mock(return_value=None)

        container_manager_mock = Mock()

        self.context._get_container_manager = Mock(return_value=container_manager_mock)

        with self.context:
            result = self.context.local_lambda_runner
            self.assertEqual(result, runner_mock)

            LambdaRuntimeMock.assert_called_with(
                container_manager_mock, image_mock, mount_symlinks=False, no_mem_limit=False
            )
            lambda_image_patch.assert_called_once_with(download_mock, True, True, invoke_images=None)
            LocalLambdaMock.assert_called_with(
                local_runtime=runtime_mock,
                function_provider=ANY,
                cwd=cwd,
                real_path=ANY,
                debug_context=None,
                env_vars_values=ANY,
                aws_profile="profile",
                aws_region="region",
                container_host="abcdef",
                container_host_interface=None,
                extra_hosts={
                    "prod-na.host": "10.11.12.13",
                    "gamma-na.host": "10.22.23.24",
                },
            )

            result = self.context.local_lambda_runner
            self.assertEqual(result, runner_mock)
            # assert that lambda runner is created only one time, and the cached version used in the second call
            self.assertEqual(LocalLambdaMock.call_count, 1)

    @patch("samcli.local.lambdafn.runtime.LambdaFunctionObserver")
    @patch("samcli.commands.local.cli_common.invoke_context.LambdaImage")
    @patch("samcli.commands.local.cli_common.invoke_context.LayerDownloader")
    @patch("samcli.commands.local.cli_common.invoke_context.LambdaRuntime")
    @patch("samcli.commands.local.cli_common.invoke_context.LocalLambdaRunner")
    @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider")
    @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext._add_account_id_to_global")
    def test_must_create_runner_with_invoke_image_option(
        self,
        _add_account_id_to_global_mock,
        SamFunctionProviderMock,
        LocalLambdaMock,
        LambdaRuntimeMock,
        download_layers_mock,
        lambda_image_patch,
        LambdaFunctionObserver_patch,
    ):
        runtime_mock = Mock()
        LambdaRuntimeMock.return_value = runtime_mock

        runner_mock = Mock()
        LocalLambdaMock.return_value = runner_mock

        download_mock = Mock()
        download_layers_mock.return_value = download_mock

        image_mock = Mock()
        lambda_image_patch.return_value = image_mock

        LambdaFunctionObserver_mock = Mock()
        LambdaFunctionObserver_patch.return_value = LambdaFunctionObserver_mock

        cwd = "cwd"
        self.context = InvokeContext(
            template_file="template_file",
            function_identifier="id",
            env_vars_file="env_vars_file",
            docker_volume_basedir="volumedir",
            docker_network="network",
            log_file="log_file",
            skip_pull_image=True,
            force_image_build=True,
            debug_ports=[1111],
            debugger_path="path-to-debugger",
            debug_args="args",
            aws_profile="profile",
            aws_region="region",
            invoke_images={None: "image"},
            mount_symlinks=False,
            no_mem_limit=False,
        )
        self.context.get_cwd = Mock()
        self.context.get_cwd.return_value = cwd

        self.context._get_stacks = Mock()
        self.context._get_stacks.return_value = [Mock()]
        self.context._get_env_vars_value = Mock()
        self.context._setup_log_file = Mock()
        self.context._get_debug_context = Mock(return_value=None)

        container_manager_mock = Mock()

        self.context._get_container_manager = Mock(return_value=container_manager_mock)

        with self.context:
            result = self.context.local_lambda_runner
            self.assertEqual(result, runner_mock)

            LambdaRuntimeMock.assert_called_with(
                container_manager_mock, image_mock, mount_symlinks=False, no_mem_limit=False
            )
            lambda_image_patch.assert_called_once_with(download_mock, True, True, invoke_images={None: "image"})
            LocalLambdaMock.assert_called_with(
                local_runtime=runtime_mock,
                function_provider=ANY,
                cwd=cwd,
                real_path=ANY,
                debug_context=None,
                env_vars_values=ANY,
                aws_profile="profile",
                aws_region="region",
                container_host=None,
                container_host_interface=None,
                extra_hosts=None,
            )

            result = self.context.local_lambda_runner
            self.assertEqual(result, runner_mock)
            # assert that lambda runner is created only one time, and the cached version used in the second call
            self.assertEqual(LocalLambdaMock.call_count, 1)


class TestInvokeContext_stdout_property(TestCase):
    @patch.object(InvokeContext, "__exit__")
    @patch("samcli.commands.local.cli_common.invoke_context.osutils.stdout")
    @patch("samcli.commands.local.cli_common.invoke_context.StreamWriter")
    @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider")
    def test_must_enable_auto_flush(self, SamFunctionProviderMock, StreamWriterMock, osutils_stdout_mock, ExitMock):
        context = InvokeContext(template_file="template")

        context._get_stacks = Mock()
        context._get_stacks.return_value = [Mock()]
        context._get_env_vars_value = Mock()
        context._setup_log_file = Mock()

        container_manager_mock = Mock()
        context._get_container_manager = Mock(return_value=container_manager_mock)

        with context:
            context.stdout

        StreamWriterMock.assert_called_once_with(ANY, auto_flush=True)

    @patch.object(InvokeContext, "__exit__")
    @patch("samcli.commands.local.cli_common.invoke_context.osutils.stdout")
    @patch("samcli.commands.local.cli_common.invoke_context.StreamWriter")
    @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider")
    def test_must_use_stdout_if_no_log_file_handle(
        self, SamFunctionProviderMock, StreamWriterMock, osutils_stdout_mock, ExitMock
    ):
        stream_writer_mock = Mock()
        StreamWriterMock.return_value = stream_writer_mock

        stdout_mock = Mock()
        osutils_stdout_mock.return_value = stdout_mock

        context = InvokeContext(template_file="template")

        context._get_stacks = Mock()
        context._get_stacks.return_value = [Mock()]
        context._get_env_vars_value = Mock()
        context._setup_log_file = Mock(return_value=None)

        container_manager_mock = Mock()
        context._get_container_manager = Mock(return_value=container_manager_mock)

        with context:
            stdout = context.stdout

            StreamWriterMock.assert_called_once_with(stdout_mock, ANY)
            self.assertEqual(stream_writer_mock, stdout)

    @patch.object(InvokeContext, "__exit__")
    @patch("samcli.commands.local.cli_common.invoke_context.StreamWriter")
    @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider")
    def test_must_use_log_file_handle(self, SamFunctionProviderMock, StreamWriterMock, ExitMock):
        stream_writer_mock = Mock()
        StreamWriterMock.return_value = stream_writer_mock

        context = InvokeContext(template_file="template")

        context._get_stacks = Mock()
        context._get_stacks.return_value = [Mock()]
        context._get_env_vars_value = Mock()

        log_file_handle_mock = Mock()
        context._setup_log_file = Mock(return_value=log_file_handle_mock)

        container_manager_mock = Mock()
        context._get_container_manager = Mock(return_value=container_manager_mock)

        with context:
            stdout = context.stdout

            StreamWriterMock.assert_called_once_with(log_file_handle_mock, ANY)
            self.assertEqual(stream_writer_mock, stdout)


class TestInvokeContext_stderr_property(TestCase):
    @patch.object(InvokeContext, "__exit__")
    @patch("samcli.commands.local.cli_common.invoke_context.osutils.stderr")
    @patch("samcli.commands.local.cli_common.invoke_context.StreamWriter")
    @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider")
    def test_must_enable_auto_flush(self, SamFunctionProviderMock, StreamWriterMock, osutils_stderr_mock, ExitMock):
        context = InvokeContext(template_file="template", debug_ports=[6000])

        context._get_stacks = Mock()
        context._get_stacks.return_value = [Mock()]
        context._get_env_vars_value = Mock()
        context._setup_log_file = Mock()

        container_manager_mock = Mock()
        context._get_container_manager = Mock(return_value=container_manager_mock)

        with context:
            context.stderr

        StreamWriterMock.assert_called_once_with(ANY, auto_flush=True)

    @patch.object(InvokeContext, "__exit__")
    @patch("samcli.commands.local.cli_common.invoke_context.osutils.stderr")
    @patch("samcli.commands.local.cli_common.invoke_context.StreamWriter")
    @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider")
    def test_must_use_stderr_if_no_log_file_handle(
        self, SamFunctionProviderMock, StreamWriterMock, osutils_stderr_mock, ExitMock
    ):
        stream_writer_mock = Mock()
        StreamWriterMock.return_value = stream_writer_mock

        stderr_mock = Mock()
        osutils_stderr_mock.return_value = stderr_mock

        context = InvokeContext(template_file="template")

        context._get_stacks = Mock()
        context._get_stacks.return_value = [Mock()]
        context._get_env_vars_value = Mock()
        context._setup_log_file = Mock(return_value=None)

        container_manager_mock = Mock()
        context._get_container_manager = Mock(return_value=container_manager_mock)

        with context:
            stderr = context.stderr

            StreamWriterMock.assert_called_once_with(stderr_mock, ANY)
            self.assertEqual(stream_writer_mock, stderr)

    @patch.object(InvokeContext, "__exit__")
    @patch("samcli.commands.local.cli_common.invoke_context.StreamWriter")
    @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider")
    def test_must_use_log_file_handle(self, SamFunctionProviderMock, StreamWriterMock, ExitMock):
        stream_writer_mock = Mock()
        StreamWriterMock.return_value = stream_writer_mock

        context = InvokeContext(template_file="template")

        context._get_stacks = Mock()
        context._get_stacks.return_value = [Mock()]
        context._get_env_vars_value = Mock()

        log_file_handle_mock = Mock()
        context._setup_log_file = Mock(return_value=log_file_handle_mock)

        container_manager_mock = Mock()
        context._get_container_manager = Mock(return_value=container_manager_mock)

        with context:
            stderr = context.stderr

            StreamWriterMock.assert_called_once_with(log_file_handle_mock, ANY)
            self.assertEqual(stream_writer_mock, stderr)


class TestInvokeContextget_cwd(TestCase):
    def test_must_return_template_file_dir_name(self):
        filename = "filename"
        context = InvokeContext(template_file=filename)

        expected = os.path.dirname(os.path.abspath(filename))
        result = context.get_cwd()

        self.assertEqual(result, expected)

    def test_must_return_docker_volume_dir(self):
        filename = "filename"
        context = InvokeContext(template_file=filename, docker_volume_basedir="basedir")

        result = context.get_cwd()
        self.assertEqual(result, "basedir")


class TestInvokeContext_get_env_vars_value(TestCase):
    def test_must_return_if_no_file(self):
        result = InvokeContext._get_env_vars_value(filename=None)
        self.assertIsNone(result, "No value must be returned")

    def test_must_read_file_and_parse_as_json(self):
        filename = "filename"
        file_data = '{"a": "b"}'
        expected = {"a": "b"}

        m = mock_open(read_data=file_data)

        with patch("samcli.commands.local.cli_common.invoke_context.open", m):
            result = InvokeContext._get_env_vars_value(filename)

            self.assertEqual(expected, result)

        m.assert_called_with(filename, "r")

    def test_must_raise_if_failed_to_parse_json(self):
        filename = "filename"
        file_data = "invalid json"

        m = mock_open(read_data=file_data)

        with patch("samcli.commands.local.cli_common.invoke_context.open", m):
            with self.assertRaises(InvalidEnvironmentVariablesFileException) as ex_ctx:
                InvokeContext._get_env_vars_value(filename)

            msg = str(ex_ctx.exception)
            self.assertTrue(
                msg.startswith("Could not read environment variables overrides from file {}".format(filename))
            )


class TestInvokeContext_setup_log_file(TestCase):
    def test_must_return_if_file_not_given(self):
        result = InvokeContext._setup_log_file(log_file=None)
        self.assertIsNone(result, "Log file must not be setup")

    def test_must_open_file_for_writing(self):
        filename = "foo"
        m = mock_open()

        with patch("samcli.commands.local.cli_common.invoke_context.open", m):
            InvokeContext._setup_log_file(filename)

        m.assert_called_with(filename, "w", encoding="utf8")


class TestInvokeContext_get_debug_context(TestCase):
    @patch("samcli.commands.local.cli_common.invoke_context.Path")
    def test_debugger_path_not_found(self, pathlib_mock):
        error = OSError()
        error.errno = errno.ENOENT
        pathlib_mock.side_effect = error

        with self.assertRaises(DebugContextException):
            InvokeContext._get_debug_context(
                debug_ports=[1111], debug_args=None, debugger_path="somepath", container_env_vars=None
            )

    @patch("samcli.commands.local.cli_common.invoke_context.Path")
    def test_debugger_path_not_dir(self, pathlib_mock):
        pathlib_path_mock = Mock()
        resolve_path_mock = Mock()
        pathlib_path_mock.resolve.return_value = resolve_path_mock
        resolve_path_mock.is_dir.return_value = False
        pathlib_mock.return_value = pathlib_path_mock

        with self.assertRaises(DebugContextException):
            InvokeContext._get_debug_context(
                debug_ports=1111, debug_args=None, debugger_path="somepath", container_env_vars=None
            )

    def test_no_debug_port(self):
        debug_context = InvokeContext._get_debug_context(None, None, None, {})

        self.assertEqual(debug_context.debugger_path, None)
        self.assertEqual(debug_context.debug_ports, None)
        self.assertEqual(debug_context.debug_args, None)
        self.assertEqual(debug_context.container_env_vars, {})

    @patch("samcli.commands.local.cli_common.invoke_context.Path")
    def test_non_path_not_found_oserror_is_thrown(self, pathlib_mock):
        pathlib_mock.side_effect = OSError()

        with self.assertRaises(OSError):
            InvokeContext._get_debug_context(
                debug_ports=1111, debug_args=None, debugger_path="somepath", container_env_vars=None
            )

    @patch("samcli.commands.local.cli_common.invoke_context.DebugContext")
    def test_debug_port_given_without_debugger_path(self, debug_context_mock):
        debug_context_mock.return_value = "I am the DebugContext"
        debug_context = InvokeContext._get_debug_context(
            debug_ports=1111, debug_args=None, debugger_path=None, container_env_vars={"env": "var"}
        )

        self.assertEqual(debug_context, "I am the DebugContext")
        debug_context_mock.assert_called_once_with(
            debug_ports=1111,
            debug_args=None,
            debugger_path=None,
            debug_function=None,
            container_env_vars={"env": "var"},
        )

    @patch("samcli.commands.local.cli_common.invoke_context.Path")
    def test_debug_port_not_specified(self, pathlib_mock):
        pathlib_path_mock = Mock()
        pathlib_mock.return_value = pathlib_path_mock

        debug_context = InvokeContext._get_debug_context(
            debug_ports=None, debug_args=None, debugger_path="somepath", container_env_vars=None
        )
        self.assertEqual(None, debug_context.debug_ports)

    @patch("samcli.commands.local.cli_common.invoke_context.Path")
    def test_container_env_vars_specified(self, pathlib_mock):
        pathlib_path_mock = Mock()
        pathlib_mock.return_value = pathlib_path_mock

        debug_context = InvokeContext._get_debug_context(
            debug_ports=1111, debug_args=None, debugger_path="somepath", container_env_vars={"env": "var"}
        )
        self.assertEqual({"env": "var"}, debug_context.container_env_vars)
        self.assertEqual(1111, debug_context.debug_ports)

    @patch("samcli.commands.local.cli_common.invoke_context.Path")
    def test_debug_port_single_value_int(self, pathlib_mock):
        pathlib_path_mock = Mock()
        pathlib_mock.return_value = pathlib_path_mock

        debug_context = InvokeContext._get_debug_context(
            debug_ports=1111, debug_args=None, debugger_path="somepath", container_env_vars={}
        )
        self.assertEqual(1111, debug_context.debug_ports)

    @patch("samcli.commands.local.cli_common.invoke_context.Path")
    def test_debug_port_single_value_string(self, pathlib_mock):
        pathlib_path_mock = Mock()
        pathlib_mock.return_value = pathlib_path_mock

        debug_context = InvokeContext._get_debug_context(
            debug_ports="1111", debug_args=None, debugger_path="somepath", container_env_vars=None
        )
        self.assertEqual("1111", debug_context.debug_ports)

    @patch("samcli.commands.local.cli_common.invoke_context.Path")
    def test_debug_port_multiple_values_string(self, pathlib_mock):
        pathlib_path_mock = Mock()
        pathlib_mock.return_value = pathlib_path_mock

        debug_context = InvokeContext._get_debug_context(
            debug_ports=["1111", "1112"], debug_args=None, debugger_path="somepath", container_env_vars=None
        )
        self.assertEqual(["1111", "1112"], debug_context.debug_ports)

    @patch("samcli.commands.local.cli_common.invoke_context.Path")
    def test_debug_port_multiple_values_int(self, pathlib_mock):
        pathlib_path_mock = Mock()
        pathlib_mock.return_value = pathlib_path_mock

        debug_context = InvokeContext._get_debug_context(
            debug_ports=[1111, 1112], debug_args=None, debugger_path="somepath", container_env_vars=None
        )
        self.assertEqual([1111, 1112], debug_context.debug_ports)

    @patch("samcli.commands.local.cli_common.invoke_context.DebugContext")
    @patch("samcli.commands.local.cli_common.invoke_context.Path")
    def test_debugger_path_resolves(self, pathlib_mock, debug_context_mock):
        pathlib_path_mock = Mock()
        resolve_path_mock = Mock()
        pathlib_path_mock.resolve.return_value = resolve_path_mock
        resolve_path_mock.is_dir.return_value = True
        resolve_path_mock.__str__ = Mock()
        resolve_path_mock.__str__.return_value = "full/path"
        pathlib_mock.return_value = pathlib_path_mock

        debug_context_mock.return_value = "I am the DebugContext"

        debug_context = InvokeContext._get_debug_context(1111, "args", "./path", None)

        self.assertEqual(debug_context, "I am the DebugContext")

        debug_context_mock.assert_called_once_with(
            debug_ports=1111, debug_args="args", debugger_path="full/path", debug_function=None, container_env_vars=None
        )
        resolve_path_mock.is_dir.assert_called_once()
        pathlib_path_mock.resolve.assert_called_once_with(strict=True)
        pathlib_mock.assert_called_once_with("./path")


class TestInvokeContext_get_stacks(TestCase):
    @patch("samcli.commands.local.cli_common.invoke_context.SamLocalStackProvider.get_stacks")
    @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext._add_account_id_to_global")
    def test_must_pass_custom_region(self, add_account_id_to_global_mock, get_stacks_mock):
        get_stacks_mock.return_value = [Mock(), []]
        invoke_context = InvokeContext("template_file", aws_region="my-custom-region")
        invoke_context._get_stacks()
        get_stacks_mock.assert_called_with(
            "template_file", parameter_overrides=None, global_parameter_overrides={"AWS::Region": "my-custom-region"}
        )


class TestInvokeContext_add_account_id_to_global(TestCase):
    @patch("samcli.commands.local.cli_common.invoke_context.get_boto_client_provider_with_config")
    def test_must_work_with_no_token(self, get_boto_client_provider_mock):
        # Mock STS client to raise exception (no credentials)
        sts_mock = Mock()
        sts_mock.get_caller_identity.side_effect = Exception("No credentials")
        get_boto_client_provider_mock.return_value.return_value = sts_mock

        invoke_context = InvokeContext("template_file")
        # Account ID should not be set when STS call fails
        self.assertIsNone(
            invoke_context._global_parameter_overrides.get("AWS::AccountId")
            if invoke_context._global_parameter_overrides
            else None
        )

    @patch("samcli.commands.local.cli_common.invoke_context.get_boto_client_provider_with_config")
    def test_must_work_with_token(self, get_boto_client_provider_with_config_mock):
        get_boto_client_provider_with_config_mock.return_value.return_value.get_caller_identity.return_value.get.return_value = (
            "210987654321"
        )
        invoke_context = InvokeContext("template_file")
        invoke_context._add_account_id_to_global()
        self.assertEqual(invoke_context._global_parameter_overrides.get("AWS::AccountId"), "210987654321")


class TestInvokeContext_validate_function_logical_ids(TestCase):
    """Tests for _validate_function_logical_ids method"""

    def create_mock_functions(self):
        """Helper method to create standard mock functions for testing"""
        func1 = Mock()
        func1.name = "Function1"
        func1.function_id = "Function1"
        func1.functionname = None
        func1.full_path = "Function1"

        func2 = Mock()
        func2.name = "Function2"
        func2.function_id = "Function2"
        func2.functionname = "MyFunction2"
        func2.full_path = "Function2"

        return func1, func2

    def test_validation_with_valid_names(self):
        """Test that no exception is raised when all function names are valid"""
        # Create mock functions
        func1, func2 = self.create_mock_functions()

        # Create InvokeContext with valid function names
        invoke_context = InvokeContext(template_file="template_file", function_logical_ids=("Function1", "MyFunction2"))

        # Mock the function provider
        function_provider_mock = Mock()
        function_provider_mock.get_all.return_value = [func1, func2]
        invoke_context._function_provider = function_provider_mock

        # Should not raise any exception
        invoke_context._validate_function_logical_ids()

    @parameterized.expand(
        [
            (("InvalidFunction", "AnotherInvalid"), "AnotherInvalid, InvalidFunction"),
            (("Function1", "InvalidFunc", "AnotherInvalid"), "AnotherInvalid, InvalidFunc"),
        ]
    )
    @patch("samcli.commands.local.cli_common.invoke_context.LOG")
    def test_validation_with_invalid_function_names(self, function_logical_ids, expected_invalid_names, log_mock):
        """Test that FunctionNotFound is raised with invalid names"""

        # Create mock functions using helper method
        func1, func2 = self.create_mock_functions()

        # Create InvokeContext with function names (some invalid)
        invoke_context = InvokeContext(template_file="template_file", function_logical_ids=function_logical_ids)

        # Mock the function provider
        function_provider_mock = Mock()
        function_provider_mock.get_all.return_value = [func1, func2]
        invoke_context._function_provider = function_provider_mock

        # Should raise FunctionNotFound
        with self.assertRaises(FunctionNotFound) as ex_ctx:
            invoke_context._validate_function_logical_ids()

        error_message = str(ex_ctx.exception)
        # Verify error message matches sam local invoke pattern
        self.assertIn("Unable to find Function(s) with name(s)", error_message)
        self.assertIn(expected_invalid_names, error_message)

        # Verify LOG.info was called with the proper message
        log_mock.info.assert_called_once()
        log_call_args = log_mock.info.call_args[0][0]
        self.assertIn("not found", log_call_args)
        self.assertIn("Possible options in your template:", log_call_args)

    @parameterized.expand(
        [
            ("none", None),
            ("empty_tuple", ()),
        ]
    )
    def test_validation_with_no_function_filter(self, test_name, function_logical_ids):
        """Test that no validation occurs when function_logical_ids is None or empty"""
        # Create InvokeContext with no function filter
        invoke_context = InvokeContext(template_file="template_file", function_logical_ids=function_logical_ids)

        # Mock the function provider (should not be called)
        function_provider_mock = Mock()
        invoke_context._function_provider = function_provider_mock

        # Should not raise any exception and should not call get_all
        invoke_context._validate_function_logical_ids()

        # Verify get_all was not called since no validation needed
        function_provider_mock.get_all.assert_not_called()

    @parameterized.expand(
        [
            ("FunctionLogicalId", "MyFunction", "FunctionLogicalId", None),
            ("MyFunction", "MyFunction", "FunctionLogicalId", None),
            ("CustomFunctionName", "MyFunction", "FunctionLogicalId", "CustomFunctionName"),
        ]
    )
    def test_validation_matches_function_attributes(self, search_name, func_name, func_id, func_functionname):
        """Test that validation matches against different function attributes"""
        # Create mock function with specified attributes
        func = Mock()
        func.name = func_name
        func.function_id = func_id
        func.functionname = func_functionname

        # Create InvokeContext using search_name
        invoke_context = InvokeContext(template_file="template_file", function_logical_ids=(search_name,))

        # Mock the function provider
        function_provider_mock = Mock()
        function_provider_mock.get_all.return_value = [func]
        invoke_context._function_provider = function_provider_mock

        # Should not raise any exception
        invoke_context._validate_function_logical_ids()
